﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace MetaSharp.Transformation.Tests.Patterns
{
	public class VariablePatterns
	{
		[Fact]
		public async Task CanDeclareVariablePatternAndUseInProduction()
		{
            var p = Pattern.Production(
                Pattern.Variable("x", Pattern.Any()),
                (int x) => x + 11);
            var m = await Pattern.Match(p, 7);

			Assert.Equal(18, m.Value);
		}

		[Fact]
		public void CanNotAccessNonDeclaredVariablesInProjection()
		{
            var p = Pattern.Production(
                Pattern.And(
                    Pattern.Variable("x", Pattern.Value(7)),
                    Pattern.Value(11)),
                (int y) => y);

            Pattern.Match(p, 7, 11).WaitForThrow<KeyNotFoundException>();
        }

		[Fact]
		public async Task CanAccessVariablesDeclaredInRule()
		{
			var expected = new int[] { 7, 11 };

			var p1 = Pattern.Rule(
				Pattern.Production(
					Pattern.Variable("x", Pattern.Any()),
					new Func<int, int>(x => x)));

			var p2 = Pattern.Production(
				Pattern.And(
					Pattern.Variable("x", p1),
					Pattern.Variable("y", p1)),
				(int x, int y) => new[] { x, y });

            var m = await Pattern.Match(p2, 7, 11);
			var values = (int[])m.Value;
			Assert.Equal(expected[0], values[0]);
			Assert.Equal(expected[1], values[1]);
		}

		[Fact]
		public async Task CanAccessVariablesDeclaredInHigherProduction()
		{
            var p = Pattern.Production(
                Pattern.And(
                    Pattern.Variable("x", Pattern.Any()),
                    Pattern.Variable("y", Pattern.Production(
                        Pattern.Any(),
                        (int x) => x))),
                (int x, int y) => x + y);
            var m = await Pattern.Match(p, 7, 11);
			Assert.Equal(14, m.Value);
		}

		[Fact]
		public async Task CanOverwriteVariablesDeclaredInHigherProduction()
		{
			var expected = new[] { 7, 11, 19 };

			// x:any (x:any any -> x) -> x
            var p = Pattern.Production(
				Pattern.And(
					Pattern.Variable("x", Pattern.Any()),
					Pattern.Production(
						Pattern.And(
							Pattern.Variable("x", Pattern.Any()),
							Pattern.Any()),
						(int x) => x)),
				(int x) => x);
            var m = await Pattern.Match(p, 7, 11, 19);
			Assert.Equal(7, m.Value);
		}

		[Fact]
		public void CannotAccessVariablesDeclaredInLowerProduction()
		{
            var p = Pattern.Production(
				Pattern.Production(
					Pattern.Variable("x", Pattern.Any()), 
                    (int x) => x),
				(int x) => x);
            Pattern.Match(p, 7).WaitForThrow<KeyNotFoundException>();
        }

		[Fact]
		public void WrongVariableNameInProductionThrows()
		{
            var p = Pattern.Production(
                Pattern.Variable("x", Pattern.Any()),
                (int y) => y);
            Pattern.Match(p, 7).WaitForThrow<KeyNotFoundException>();
        }

		[Fact]
		public async Task CanAssignObjectAndPropertyToDifferentVariables()
		{
			var p = Pattern.Production(
				Pattern.Variable(
					"o",
					Pattern.Object(
						typeof(object),
						Pattern.Property("x", Pattern.Variable("x", Pattern.Any())))),
				(object o, int x) => new[] { o, x });
            var instance = new { x = 7 };
            var m = await Pattern.Match(p, instance);

			var values = (object[])m.Value;
			Assert.Same(instance, values[0]);
			Assert.Equal(7, values[1]);
		}

		[Fact]
		public async Task DirectLeftRecursionWithVerificationSwitchIsSuccessful()
		{
            var m = await Pattern.Match(DlrTest2.A, "x.y.z");
			var actual = (expr)m.Value;
			Assert.Equal("z", actual.Name);
			Assert.Equal("y", actual.Target.Name);
			Assert.Equal("x", actual.Target.Target.Name);
			Assert.Null(actual.Target.Target.Target);
		}

		[Fact]
		public async Task DirectLeftRecursionWithVerificationSwitchCorrectlyFails()
		{
			var result = await Pattern.Match(DlrTest2.A, "x.y.x");
			Assert.False(result.Matched);
		}

		[Fact]
		public async Task CallParameterizedPattern()
		{
			var and = Pattern.Closure(p => Pattern.And(p[0], p[1]));
			var m = await Pattern.Match(
				Pattern.Call(
					and,
					Pattern.Value('a'),
					Pattern.Value('b')),
                "ab");

            Assert.Equal(new object[] { 'a', 'b' }, m.Value);
		}

		[Fact]
		public async Task DirectLeftRecursionOnCallablePatternIsSuccessful()
		{
			var result = await Pattern.Match(DlrTest2.A, "x.y.z");
			var actual = (expr)result.Value;
			Assert.Equal("z", actual.Name);
			Assert.Equal("y", actual.Target.Name);
			Assert.Equal("x", actual.Target.Target.Name);
			Assert.Null(actual.Target.Target.Target);
		}

		class expr
		{
			public string Name { get; set; }
			public expr Target { get; set; }
		}

		class DlrTest2
		{
			// A = b:B s:(expr { Name = 'z' })(b) -> b; //switch variable b
			// B = b:B '.' c:C -> { c.Target = b; return c; }
			//   | C
			// C = c:'a'..'z' -> new expr { Name = c.ToString() };

            public static Pattern A, B, C;

            static DlrTest2()
            {
                C = Pattern.Rule(
                    Pattern.Production(
                        Pattern.Variable("n", Pattern.Range('a', 'z')),
                        (char n) => new expr { Name = "" + n }));

                B = Pattern.Rule(
                    Pattern.Or(
                        Pattern.Production(
                            Pattern.And(
                                Pattern.Variable("b", Pattern.Reference(() => B)),
                                Pattern.Value('.'),
                                Pattern.Variable("c", C)),
                            (expr b, expr c) =>
                            {
                                c.Target = b;
                                return c;
                            }),
                        C));

                A = Pattern.Rule(
                    Pattern.Production(
                        Pattern.And(
                            Pattern.Variable("b", B),
                            Pattern.Variable("c",
                                Pattern.Switch(
                                    "b",
                                    Pattern.Object(
                                        typeof(expr),
                                        Pattern.Property(
                                            "Name",
                                            Pattern.Value("z")))))),
                        (expr c) => c));
            }
		}

	}
}
